/** file nbalsa.cpp
    author belot.nicolas
*/

#include "nbalsa.h"
#ifdef HAVE_LIBALSA
#include <iostream>
#include <pthread.h>
namespace libtrckr {
	union AlsaConv {
		double *d;//double 64
		float *f; // float 32
		int32_t *i; // int 32
		int32_t *m; //int 24
		int16_t *s; //int 16
	};

	# define INT8_MAX               (127)
	# define INT16_MAX              (32767)
	# define INT24_MAX		(8388607)
	# define INT32_MAX              (2147483647)

	bool alsa_open=false;
	snd_pcm_uframes_t bsize;
	double alsa_buffer[2048];
	AlsaConv alsa_inbuff;
	nbalsa_cb_t *_cb_alsa;
	void *alsa_arg;
	bool alsa_full=false;
	pthread_t alsa_threads[2];
	pthread_mutex_t alsa_mutin, alsa_mutcount;
	pthread_cond_t alsa_isFull, alsa_isEmpty;
	short alsa_size;

	void *alsa_write(void *arg){
		int errorcode;
		snd_pcm_t *stream = (snd_pcm_t*)arg;
		
		while(alsa_open){
			if(alsa_full){
				for(int i=0;i<bsize*2;i++){
					if(alsa_size==64){
						alsa_inbuff.d[i]=alsa_buffer[i];
					}else if(alsa_size==-32){
						alsa_inbuff.f[i]=float(alsa_buffer[i]);
					}else if(alsa_size==32){
						alsa_inbuff.i[i]=INT32_MAX * alsa_buffer[i];
					}else if(alsa_size==24){
						alsa_inbuff.m[i]=INT24_MAX * alsa_buffer[i];
					}else if(alsa_size==16){
						alsa_inbuff.s[i]=INT16_MAX * alsa_buffer[i];
					}
					//alsa_inbuff[i]=alsa_buffer[i];
				}
				alsa_full=false;
				int cnt=bsize;
				int offset=0;
				while(cnt > 0){
					//check for cancel...
					pthread_testcancel();
					if(alsa_size==64){
						errorcode=snd_pcm_writei(stream, alsa_inbuff.d+offset, cnt);
					}else if(alsa_size==-32){
						errorcode=snd_pcm_writei(stream, alsa_inbuff.f+offset, cnt);
					}else if(alsa_size==32){
						errorcode=snd_pcm_writei(stream, alsa_inbuff.i+offset, cnt);
					}else if(alsa_size==24){
						errorcode=snd_pcm_writei(stream, alsa_inbuff.m+offset, cnt);
					}else if(alsa_size==16){
						errorcode=snd_pcm_writei(stream, alsa_inbuff.s+offset, cnt);
					}
					//errorcode=snd_pcm_writei(stream, alsa_buffer, 2048);
					if(errorcode == -EAGAIN)
						continue;
					else if(errorcode == -EPIPE){
						if( snd_pcm_prepare (stream) < 0){
							exit(0);
						}
					}else if(errorcode<0){
						std::cerr << snd_strerror(errorcode) << std::endl;
						break;
	
					}else {
						//under run occured ?
						//std::cout << "write : " << errorcode <<std::endl;
						cnt-=errorcode;
						offset+=errorcode*2;
						//std::cout << alsa_inbuff.d+offset << std::endl;
					}
					
				}

			}else{
				if(_cb_alsa(bsize,alsa_arg)>0){//2 channel
					alsa_full=true;
				}else{
					usleep(10000);
				}
			}
			pthread_testcancel();
		}
		pthread_exit(NULL);
	}


	/*void *alsa_fill(void *arg){
		while(alsa_open){
			pthread_mutex_lock(&alsa_mutcount);
			if(alsa_full){
				pthread_cond_signal(&alsa_isFull);
				pthread_cond_wait(&alsa_isEmpty,&alsa_mutcount);
			}else{
				pthread_mutex_lock(&alsa_mutin);
				if(_cb_alsa(bsize,alsa_arg)>0){//2 channel
					pthread_mutex_unlock(&alsa_mutin);
					alsa_full=true;
				}else{
					pthread_mutex_unlock(&alsa_mutin);
					usleep(10000);
				}
			}
			pthread_mutex_unlock(&alsa_mutcount);
			pthread_testcancel();
		}
		pthread_exit(NULL);
	}*/
	

	double *alsa_getIn(){
		return alsa_buffer;
	}

	snd_pcm_t *alsa_get_stream(uint* mrate, short* msize){
		int err;
		snd_pcm_t *handle;
		snd_pcm_hw_params_t *hw_params;

		unsigned int rate=*mrate;
		short size=*msize;
		if( (err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0))<0){
			std::cerr << "can not open audio devices plughw:0,0 : " << snd_strerror(err) << std::endl;
			return NULL;
		}
		if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
			std::cerr << "cannot allocate hardware parameter structure "<<
			snd_strerror (err) << std::endl;
			snd_pcm_close (handle);
			return NULL;
		}
		if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) {
			std::cerr << "cannot initialize hardware parameter structure " <<  snd_strerror (err) << std::endl;
			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
			return NULL;
		}
		if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
			std::cerr << "cannot set access type " <<  snd_strerror (err) << std::endl; 
			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
			return NULL;
		}
		//We will check for sample format near our (ie size)
		bool find=false;
		if(size==64 && !find){
			if( snd_pcm_hw_params_test_format(handle,hw_params, SND_PCM_FORMAT_FLOAT64)==0){
				//format available
				find= (snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_FLOAT64)==0);
				*msize=64;
				alsa_inbuff.d=(double*)malloc(sizeof(double)*2048);
				
			}else 
				size=-32;
			
		}
		if(size==-32 && !find){
			if( snd_pcm_hw_params_test_format(handle,hw_params, SND_PCM_FORMAT_FLOAT)==0){
				//format available
				find= (snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_FLOAT)==0);
				*msize=-32;
				alsa_inbuff.f=(float*)malloc(sizeof(float)*2048);
			}else
				size=32;
		}
		if(size==32 && !find){
			if( snd_pcm_hw_params_test_format(handle,hw_params, SND_PCM_FORMAT_S32)==0){
				//format available
				find= (snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S32)==0);
				*msize=32;
				alsa_inbuff.i=(int32_t*)malloc(sizeof(int32_t)*2048);
			}else
				size=24;
		}

		if(size==24 &&!find){
			if( snd_pcm_hw_params_test_format(handle,hw_params, SND_PCM_FORMAT_S24)==0){
				//format available
				find= (snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S24)==0);
				*msize=24;
				alsa_inbuff.m=(int32_t*)malloc(sizeof(int32_t)*2048);
			}else
				size=16;
		}		

		if(size==16 &&!find){
			if( snd_pcm_hw_params_test_format(handle,hw_params, SND_PCM_FORMAT_S16)==0){
				//format available
				find= (snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16)==0);
				*msize=16;
				alsa_inbuff.s=(int16_t*)malloc(sizeof(int16_t)*2048);
			}
		}
		if(!find){
			std::cerr << "Error setting format" << std::endl;
			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close(handle);
			return NULL;
		}
		//setting rate
		if (snd_pcm_hw_params_set_rate_near(handle, hw_params, &rate, 0) < 0) {
     			std::cerr<< "Error setting rate." << std::endl;
			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
      			return NULL;
		}
		if (snd_pcm_hw_params_set_channels(handle, hw_params, 2) < 0) {
     			std::cerr<< "Error setting channels."<<std::endl;
      			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
      			return NULL;
    		}

		*mrate=rate;
		  /* Set number of periods. Periods used to be called fragments. */ 
    		if (snd_pcm_hw_params_set_periods(handle, hw_params, 2, 0) < 0) {
      			std::cerr<<"Error setting periods." << std::endl;
      			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
      			return NULL;
    		}
		snd_pcm_hw_params_get_buffer_size(hw_params,&bsize );
		bsize/=2;
		if(bsize ==0){
			int min = bsize;
			snd_pcm_hw_params_get_buffer_size_max(hw_params,&bsize );
			int max=bsize/2;
			if(max > 1024){
				bsize=2048;
			}
			snd_pcm_hw_params_set_buffer_size(handle,hw_params,bsize );
			snd_pcm_hw_params_get_buffer_size(hw_params,&bsize );
			bsize/=2;
			std::cout << bsize << std::endl;
			if(bsize==0){
				std::cerr << "No place for buffer..." << std::endl;
				snd_pcm_hw_params_free (hw_params);
				snd_pcm_close (handle);
      				return NULL;
			}
		}
		if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) {
			std::cerr << "cannot set parameters " << snd_strerror (err)<< std::endl;
			snd_pcm_hw_params_free (hw_params);
			snd_pcm_close (handle);
      			return NULL;
		}

		snd_pcm_hw_params_free (hw_params);

		if ((err = snd_pcm_prepare (handle)) < 0) {
			std::cerr<< "cannot prepare audio interface for use"<< snd_strerror (err)<< std::endl;
			snd_pcm_close (handle);
      			return NULL;
		}

		alsa_open=true;
		//
		alsa_size=*msize;

		//alsa_in=malloc(sizeof(double)*2048);
		/*pthread_mutex_init(&alsa_mutcount,NULL);
		pthread_mutex_init(&alsa_mutin,NULL);

		pthread_cond_init(&alsa_isFull,NULL);
		pthread_cond_init(&alsa_isEmpty,NULL);
*/
		pthread_create(&alsa_threads[0],NULL,alsa_write,(void*) handle);
//		pthread_create(&alsa_threads[1],NULL,alsa_fill, NULL);


		return handle;
}

void alsa_close_stream(snd_pcm_t *stream){
	//should cancel thread...
	int err;
	std::cout << "stopping thread..."<< std::endl;
//	pthread_cancel(alsa_threads[1]);
	pthread_cancel(alsa_threads[0]);pthread_join(alsa_threads[0],NULL);
	if( (err= snd_pcm_close(stream))< 0){
		std::cerr<< "cannot prepare audio interface for use"<< snd_strerror (err)<< std::endl;
		exit(1);
	}
	if(alsa_size==64){
		free(alsa_inbuff.d);
	}else if(alsa_size==-32){
		free(alsa_inbuff.f);
	}else if(alsa_size==32){
		free(alsa_inbuff.i);
	}else if(alsa_size==24){
		free(alsa_inbuff.m);
	}else if(alsa_size==16){
		free(alsa_inbuff.s);
	}
}

void alsa_set_callback(	nbalsa_cb_t *func, void* arg){
	_cb_alsa=func;
	alsa_arg=arg;
}

}

#endif
